/*
 * Copyright 2016-2019 yoara
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package yhao.infra.service.datasource;

import org.apache.ibatis.session.ExecutorType;
import org.apache.ibatis.session.RowBounds;
import org.apache.ibatis.session.SqlSession;
import org.mybatis.spring.MyBatisExceptionTranslator;
import org.mybatis.spring.SqlSessionUtils;
import org.mybatis.spring.support.SqlSessionDaoSupport;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.jdbc.core.JdbcTemplate;
import yhao.infra.common.model.Pagination;
import yhao.infra.service.datasource.mybatis.SqlSessionCallback;
import yhao.infra.service.datasource.mybatis.interceptor.PaginationInterceptor;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * Dao基类
 * <p>扩展MyBatis功能，或实现一些通用的数据操作
 *
 * @see BaseDao
 * @author yoara
 */
public abstract class BaseDaoImpl extends SqlSessionDaoSupport implements BaseDao {
	private Logger logger = LoggerFactory.getLogger(this.getClass());
	/*每次批量提交commit最大数据条数*/
	private static final int MAX_BATCH_SIZE = 5000;
	abstract protected JdbcTemplate getJdbcTemplate();

	/**
	 * 自定义操作执行
	 * 
	 * @param sqlSessionCallback
	 * 用法参考@see #batchInsert(List, String)
	 * @return
	 */
	@Override
	public Object execute(SqlSessionCallback sqlSessionCallback, ExecutorType type) {
		SqlSession sqlSession = SqlSessionUtils.getSqlSession(
				getSqlSessionFactory(), type,
				new MyBatisExceptionTranslator(getSqlSessionFactory()
						.getConfiguration().getEnvironment().getDataSource(),true));
		try {
			return sqlSessionCallback.doInSqlSession(sqlSession);
		} catch (Exception e) {
			logger.error(e.getMessage(),e);
			throw new RuntimeException(e);
		} finally {
			SqlSessionUtils.closeSqlSession(sqlSession, getSqlSessionFactory());
		}
	}


	@Override
	public <T> Pagination<T> selectList(String statement, String statementCount, Object parameter,
										Pagination<T> page) {
		if (page == null) {
			page = new Pagination<>();
		}
		if (page.isQueryRecordCount()) {
			int count = selectCount(statementCount, parameter);
			page.setRecordCount(count);
		}
		if(!page.isQueryRecordCount()|| page.getRecordCount()>0){
			List<T> items = getSqlSession().selectList(
					statement,
					parameter,
					new RowBounds(page.getCurrentPageStartIndex(), page
							.getPageSize()));
			page.setItems(items);
		}else{
			page.setItems(new ArrayList<>());
		}
		return page;
	}

	@Override
	public int selectCount(String statement, Object parameter) {
		return (Integer) getSqlSession().selectOne(statement,
				new PaginationInterceptor.CountParameter(parameter));
	}

	@Override
	public int update(String statement, Object parameter) {
		return proxy(statement, parameter, 3);
	}

	@Override
	public int delete(String statement, Object parameter) {
		return proxy(statement, parameter, 2);
	}

	@Override
	public List<?> selectList(String statement) {
		return getSqlSession().selectList(statement, null);
	}

	@Override
	public int insert(String statement, Object parameter) {
		return proxy(statement, parameter, 1);
	}

	@Override
	public <T> List<T> selectList(String statement, Object parameter,
			int pageSize, int pageIndex) {
		List<T> items = getSqlSession().selectList(statement, parameter,
				new RowBounds((pageIndex - 1) * pageSize, pageSize));
		return items;
	}

	@Override
	public List<?> selectList(String statement, Object parameter) {
		return getSqlSession().selectList(statement, parameter);
	}

	@Override
	public void executeSql(String sql) {
		getJdbcTemplate().execute(sql);
	}

	private int proxy(String statement, Object parameter, int type) {
		int result = 0;
		try {
			switch (type) {
			case 1:
				result = getSqlSession().insert(statement, parameter);
				break;
			case 2:
				result = getSqlSession().delete(statement, parameter);
				break;
			case 3:
				result = getSqlSession().update(statement, parameter);
				break;
			}
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
		return result;
	}

	/** 批量导入 **/
	public void batchInsert(final List<Map<String, Object>> list,
			final String statement) {
		execute(sqlSession -> {
            for (Map<String, Object> map : list) {
                insert(statement, map);
            }
            return list;
        },ExecutorType.BATCH);
	}
	
	@SuppressWarnings("rawtypes")
	@Override
	public Object selectOne(String statement, Object parameter) {
		List result = selectList(statement, parameter);
		if(result!=null&&result.size()>0){
			if(result.size()>1){
				logger.warn("more then 1 result:"+statement);
			}
			return result.get(0);
		}
		return null;
	}

	@Override
	public void executeBatch(List<?> list, String statement, int batchSize) {
		int commitSize = Math.min(batchSize, MAX_BATCH_SIZE);
		execute(sqlSession -> {
			for (int i = 0; i < list.size(); i++) {
				sqlSession.update(statement, list.get(i));
				if ((i + 1) % commitSize == 0) {
					sqlSession.commit();
				}
			}
			sqlSession.commit();
			return list;
		}, ExecutorType.BATCH);
	}

	@Override
	public SqlSession getSqlSession() {
		return super.getSqlSession();
	}
}
